package com.instpower.things.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import com.instpower.things.config.Configs;
import com.instpower.things.config.OtaStatus;
import com.instpower.things.entity.Battery;
import com.instpower.things.entity.Cabinet;
import com.instpower.things.entity.CabinetCmdLog;
import com.instpower.things.helper.DateUtil;
import com.instpower.things.service.InstService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @ClassName InstServiceImpl
 * @Author: hellmash@gmail.com
 * @Date: 2020/3/5
 * @Version: 1.0.0
 * @Description: TODO
 */
@Service(value = "instService")
public class InstServiceImpl implements InstService {

    @Value("${things.timeZone}")
    private String zoneId;

    @Autowired
    private ThreadPoolTaskExecutor taskExecutor ;


    private final static Logger logger = LoggerFactory.getLogger("CORE");

    /**
     * 查看该订单是否可以执行借出指令
     */
    private final static String QUERY_ORDER_STATUS = "select id from inst_order where id=? and order_status=\"pending\"";

    /**
     * 获取机柜号NO
     */
    private final static String QUERY_CABINET_NO      = "select cabinet_no from inst_cabinet where cabinet_sn=?";

    /**
     * 电池状态更新
     */
    private final static String UPDATE_BATTERY_INFO = "update inst_battery set available_status=?, " +
            "error_status=?, cabinet_sn=?, battery_status = 'in_cabinet', slot_num=?, update_time=?,cabinet_no=? ,battery_power=?" +
            " where " +
            "battery_sn=?";

    /**
     * 查询机柜的电池
     */
    private final static String QUERY_CABINET_BATTERY     = "select battery_sn from inst_battery where cabinet_sn = ? and battery_status='in_cabinet' ";


    /**
     * 修改机柜中错误的电池状态
     */
    private final static String UPDATE_ERROR_BATTERY     = "update  inst_battery set " +
            "cabinet_sn='',cabinet_no = '',slot_num = null,battery_power =0,battery_status = 'eject_normally',available_status =0 where battery_sn in ( ? )";




    /**
     * 查询机柜绑定和锁定状态
     */
    private final static String SELECT_CABINET        = "select id, merchant_id,bind_status,  locked_flag, " +
            "firmware_version " +
            " from " +
            "inst_cabinet where cabinet_sn=?";
    /**
     * 机柜登录状态更新
     */
    private final static String UPDATE_CABINET_STATUS = "update inst_cabinet set network_status =%s, online_status=%s,firmware_version='%s',update_time='%s' where cabinet_sn='%s'";

    /**
     * 机柜登录状态更新
     */
    private final static String UPDATE_CABINET_STATUS_WITHOUT_VERSION = "update inst_cabinet set network_status =%s, online_status=%s,update_time='%s' where cabinet_sn='%s'";

    /**
     * 机柜心跳状态更新
     */
    private final static String UPDATE_CABINET_BEAT = "update inst_cabinet set network_status =%s, update_time = '%s',rent_num=%s,return_num = %s, available_status = %s where cabinet_sn='%s'";

    /**
     * 获取升级任务详情
     */
    private final static String OTA_TASK_INFO = "select ota_type,start_time,end_time,ota_version,ota_url,ota_status,cabinet_sn," +
            "file_length,check_sum from inst_ota where id=?";

    /**
     * 更新更新计划状态
     */
    private final static String OTA_TASK_UPDATE = "update inst_ota set ota_status=? where id=?";

    /**
     * 单个机柜更新完毕后更新指令状态
     */
    private final static String OTA_CABINET_UPDATE_STATUS = "update inst_ota set ota_status=? where id=? and " +
            "ota_status='execute'";

    /**
     * 获取未升级的设备信息
     */
    private final static String OTA_BATCH_CABINET = "select id,cabinet_sn,network_status from inst_cabinet where " +
            "id>? and firmware_version!=? order by id asc limit ?";

    /**
     * 机柜升级日志打印
     */
    private final static String CREATE_CABINET_CMD_LOG = "INSERT INTO inst_cabinet_cmd_log (cabinet_sn, cabinet_cmd, cmd_code" +
            ", cabinet_log,create_time,session_id) VALUES (?,?,?,?,?,?)";

    /**
     * ota升级参数
     */
    private final static String OTA_CONFIG_PARAMS = "SELECT key_name,key_value FROM sys_system where key_name in " +
            "('ota_delay_minutes','ota_fetch_limit')";

    /**
     * 查询当天设备在线状态
     */
    private  final static String QUERY_CABINET_LOG_FOR_DATE = "SELECT id,beat_num from inst_cabinet_log where " +
            "log_date=? and cabinet_sn=?";


    /**
     * 增加设备登录记录
     */
    private final static String INSERT_CABINET_LOG  = "INSERT INTO inst_cabinet_log (cabinet_id, cabinet_sn, " +
            "merchant_id,log_date,beat_num) VALUES (?, ?, ?, ?,1)";

    /**
     * 更新设备心跳
     */
    private final static String UPDATE_CABINET_LOG  = "UPDATE inst_cabinet_log set login_time=?,beat_num=beat_num+1 " +
            "where id=?";


    /**
     * 查询系统参数的值
     */
    private final static String SELECT_SYSTEM    = "select  key_value from sys_system where key_name=? and deleted = 0";


    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public Boolean checkOrderStatus(int orderId) {
        try {
            jdbcTemplate.queryForMap(QUERY_ORDER_STATUS, orderId);
            return Boolean.TRUE;
        } catch (EmptyResultDataAccessException e) {
            logger.debug("订单状态已经发生变化，不做处理!");
            return Boolean.FALSE;
        }
    }

    @Override
    public Boolean  updateBatteryStatus(String cabinetSn, List<Battery> batteries) {
        taskExecutor.execute(()->{
            LocalDateTime now = LocalDateTime.now(ZoneId.of(zoneId));
            String nowStr = DateUtil.formatDateTime(now);

            String cabinetNo = "0";

            Map<String, Object> result = new HashMap<>();
            try {
                result = jdbcTemplate.queryForMap(QUERY_CABINET_NO, cabinetSn);
            } catch (IncorrectResultSizeDataAccessException e) {
                logger.error("机柜SN未入库或存在多个，sn:{}", cabinetSn);
            }
            if(!CollectionUtils.isEmpty(result)){
                cabinetNo = (String)result.get("cabinet_no");
            }
            // handler old data
            List<Map<String, Object>> maps = jdbcTemplate.queryForList(QUERY_CABINET_BATTERY, cabinetSn);
            Set<String> batterySnSet = new HashSet<>();
            if(!CollectionUtils.isEmpty(maps)){
                batterySnSet = maps.stream().map(t -> (String)t.get("battery_sn")).collect(Collectors.toSet());
            }
            Set<String> finalBatterySnSet = batterySnSet;

            //批量更新电池状态
            String finalCabinetNo = cabinetNo;
            try {
                jdbcTemplate.batchUpdate(UPDATE_BATTERY_INFO, new BatchPreparedStatementSetter() {
                    @Override
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        //电池可借，表示电池没问题
                        Battery battery = batteries.get(i);
                        String error = "none";
                        if ((battery.getSlotFlag() & 4) == 4) {
                            //可用
                            ps.setInt(1, 1);
                        } else {
                            //不可用
                            ps.setInt(1, 0);
                        }
                        //电池没问题则为0
                        //Bit1：电池虚电自检标志 1：虚电 0：未虚 Bit2：放电温度过高报警标 1：过高 0：正常 Bit3：充电温度过高报警标志 1：过高 0：正常
                        //是否有故障,none:无故障,damaged:虚电,failure:有故障
                        if((2 & battery.getFullFlag()) == 2){
                            error = "damaged";
                        }
                        if ((battery.getFullFlag() & 28) == 28){
                            error = "failure";
                        }
                        ps.setString(2, error);
                        ps.setString(3, battery.getCabinetSn());
                        ps.setInt(4, battery.getSlotNum());
                        ps.setString(5, nowStr);
                        ps.setString(6, finalCabinetNo);
                        ps.setInt(7, battery.getFullPer());
                        ps.setString(8, battery.getSn());
                        logger.info("电池跟新,数据:CabinetSn:{},SlotNum:{},nowStr:{},finalCabinetNo:{},FullPer:{},battery.getSn:{}", battery.getCabinetSn(),battery.getSlotNum(),nowStr,finalCabinetNo,battery.getFullPer(),battery.getSn());
                        if(finalBatterySnSet.contains(battery.getSn())){
                            finalBatterySnSet.remove(battery.getSn());
                        }
                    }

                    @Override
                    public int getBatchSize() {
                        return batteries.size();
                    }
                });

                if(!CollectionUtils.isEmpty(finalBatterySnSet)){
                    logger.error("机柜更新电池,发现异常电池信息,电池数据为:{}",finalBatterySnSet);
                    StringBuffer sb =new StringBuffer();
                    for (String batterySn : finalBatterySnSet) {
                        sb.append(batterySn).append(",");
                    }
                    String param = sb.toString();
                    param =param.substring(0,param.length()-1);
                    jdbcTemplate.update(UPDATE_ERROR_BATTERY,param);
                }


            }catch (Exception e){
                logger.error("updateBatteryStatus 出问题,机柜号:{},e:{}",cabinetSn, e.getMessage());
                for (Battery battery : batteries) {
                    logger.error("updateBatteryStatus 出问题,电池的信息是{}",battery);
                }
            }
        });
        return Boolean.TRUE;
    }


    @Override
    public void updateCabinetOnline(String sn, String version) {
        taskExecutor.execute(()->{
            this.updateCabinetStatus(sn, Boolean.TRUE, version);
        });
    }

    @Override
    public void updateCabinetOffline(String sn) {
        this.updateCabinetStatus(sn, Boolean.FALSE, null);
    }

    /**
     * 处理心跳包,数据记录到数据库口，增加一个1个小时失效的缓存，用于下次再放入数据库,更新机柜状态
     * @param sn
     */
    @Transactional
    @Override
    public void updateCabinetBeat(String sn,boolean isAvailable, int rentNum, int returnNum) {
        logger.info("上报机柜心跳处理方法请求成功，请求参数，sn:{},rentNum:{},returnNum:{}",sn,rentNum,returnNum);
        LocalDateTime now = LocalDateTime.now(ZoneId.of(zoneId));
        String nowString = DateUtil.formatDateTime(now);
        logger.info("上报机柜心跳处理,更新机柜方法，请求参数，sn:{},rentNum:{},returnNum:{},network_status:{},update_time:{}",sn,rentNum,returnNum,1,nowString);
        jdbcTemplate.update(String.format(UPDATE_CABINET_BEAT,1,nowString,rentNum,returnNum,isAvailable,sn));
    }

    @Override
    public Map<String, Object> findTaskByKey(Integer taskId) {
        try {
            return jdbcTemplate.queryForMap(OTA_TASK_INFO, taskId);
        }catch (Exception e){
            return null;
        }
    }

    @Override
    public List<Map<String, Object>> findCabinet(int cabinetId, String firmwareVersion, int pageSize){
        return jdbcTemplate.queryForList(OTA_BATCH_CABINET, cabinetId,firmwareVersion, pageSize);
    }

    @Override
    public Cabinet getCachedCabinet(String cabinetSn) {
        try {
            String cabinetInfo = redisTemplate.opsForValue().get(cabinetSn);
            if (!StringUtils.isEmpty(cabinetInfo)) {
                return objectMapper.readValue(cabinetInfo, Cabinet.class);
            }
        } catch (Exception e) {
            logger.error("获取机柜 {} 缓存信息失败!", cabinetSn);
            return null;
        }

        return null;
    }

    @Override
    public Boolean compareCachedCabinet(Cabinet cachedCabinet, Cabinet reportedCabinet) {
        // check return/rent num is the same
        return reportedCabinet.getRentNum() == cachedCabinet.getRentNum()
                && (reportedCabinet.getReturnNum() == cachedCabinet.getReturnNum());
    }

    @Override
    public Battery getCachedBattery(String batterySn) {
        try {
            String batteryInfo = (String) redisTemplate.opsForHash().get(Configs.BATTERY_HASHKEY, batterySn);
            if (!StringUtils.isEmpty(batteryInfo)) {
                return objectMapper.readValue(batteryInfo, Battery.class);
            }
        } catch (Exception e) {
            logger.error("获取电池 {} 缓存信息失败!", batterySn);
            return null;
        }

        return null;
    }

    @Override
    public Boolean compareCachedBattery(Battery cachedBattery, Battery reportedBattery) {
        return ((reportedBattery.getCabinetSn().equals(cachedBattery.getCabinetSn()))
                && (reportedBattery.getSlotNum() == cachedBattery.getSlotNum())
                && (reportedBattery.getFullFlag() == cachedBattery.getFullFlag()));
    }

    @Override
    public List<Map<String, Object>> findOtaConfig() {
        return jdbcTemplate.queryForList(OTA_CONFIG_PARAMS);
    }

    @Override
    public void logCabinetCmd(CabinetCmdLog log) {
        taskExecutor.execute(()->{
            LocalDateTime now = LocalDateTime.now(ZoneId.of(zoneId));
            jdbcTemplate.update(CREATE_CABINET_CMD_LOG, log.getCabinetSn(), log.getCabinetCmd().val(), log.getCmdCode(),
                    log.getCabinetLog(), DateUtil.formatDateTime(now), log.getSessionId());
        });
    }

    @Override
    public void updateTask(Integer taskId, OtaStatus status) {
        jdbcTemplate.update(OTA_TASK_UPDATE, status.Val(), taskId);
    }

    @Override
    public void updateCabinetOtaStatus(Integer taskId, OtaStatus status) {
        taskExecutor.execute(()->{
            jdbcTemplate.update(OTA_CABINET_UPDATE_STATUS, status.Val(), taskId);
        });
    }

    /**
     *
     * @param status TRUE ： 上线，FALSE：下线
     * @return
     */
    @Transactional
    void updateCabinetStatus(String sn, Boolean status, String firmwareVersion){
        int online_status = 0;
        Map<String, Object> map;
        try {
            map = jdbcTemplate.queryForMap(SELECT_CABINET, sn);
        } catch (IncorrectResultSizeDataAccessException e) {
            logger.error("机柜SN未入库或存在多个，sn:{}", sn);
            return;
        }
        if(CollectionUtils.isEmpty(map))
            return;
        if(status){
            String bind_stats = map.get("bind_status")==null?"":(String)map.get("bind_status");
            boolean locked_flag = (boolean)map.get("locked_flag");
            //未锁住，bing状态
            if("bind".equals(bind_stats) && !locked_flag){
                online_status = 1;
            }
        }
        LocalDateTime now = LocalDateTime.now(ZoneId.of(zoneId));
        if(StringUtils.hasText(sn) && sn.length() >8){
            if(!StringUtils.isEmpty(firmwareVersion)) {
                logger.info("修改机柜信息,sn:{},online_status:{}",sn,online_status);
                jdbcTemplate.update(String.format(UPDATE_CABINET_STATUS, status ? 1:0, online_status, firmwareVersion,DateUtil.formatDateTime(now),sn));
            } else {
                logger.info("修改机柜信息 with_version,sn:{},online_status:{}",sn,online_status);
                jdbcTemplate.update(String.format(UPDATE_CABINET_STATUS_WITHOUT_VERSION, status ? 1:0, online_status, DateUtil.formatDateTime(now),sn));
            }

        }
    }

    @Override
    public void updateCabinetLog(String cabinetSn) {
        LocalDateTime now = LocalDateTime.now(ZoneId.of(zoneId));
        String nowDate = DateUtil.formatDate(now);
        try {
            Map<String, Object> result = jdbcTemplate.queryForMap(QUERY_CABINET_LOG_FOR_DATE, nowDate, cabinetSn);
        }catch (Exception e){
            /**
             * 插入数据
             */
            Map<String, Object> map = jdbcTemplate.queryForMap(SELECT_CABINET, cabinetSn);
            if(null == map)
                return;
            int cabinetId = (int)map.get("id");
            int merchantId = 0;
            Object merchant = map.get("merchant_id");
            if(merchant != null)
                merchantId = (int)merchant;
            jdbcTemplate.update(INSERT_CABINET_LOG, cabinetId, cabinetSn, merchantId, nowDate);
        }
    }

    @Override
    public String querySystemData(String keyName) {
        String keyValue = redisTemplate.opsForValue().get("SystemConfig_" + keyName);
        if(!StringUtils.isEmpty(keyValue)){
            return keyValue;
        }
        Map<String, Object> map = jdbcTemplate.queryForMap(SELECT_SYSTEM, keyName);
        if(!CollectionUtils.isEmpty(map)){
            keyValue = (String) map.get("key_value");
            redisTemplate.opsForValue().set("SystemConfig_" + keyName,keyValue);
        }
        return keyValue;
    }




}
